package com.mikelouis.test.scala.impatient.chapter14_pattern_matching

import scala.reflect.ClassTag

/**
 * Created by yuazhu on 4/24/2015.
 */
object Chapter14 extends App {
  // 2
  def swapPair[A, B](p: (A, B)) = p match {
    case (y, x) => (x, y)
    case _ => new IllegalArgumentException("Invalid Tuple2")
  }

  println(swapPair(1, 2))

  // 3
  /**
   * To instantiate an array in a generic context (instantiating an array of T where T is a type parameter),
   * scala needs at runtime to have information about T, in the form of an implicit value of type ClassTag[T].
   * Concretely, you just need to require from the caller of your method to (implicitly) pass this ClassTag value,
   * which can conveniently be done using a context bound:
   *
   * For a (torough) description of this situation, see this document:
   *
   * http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html
   * http://docs.scala-lang.org/sips/completed/scala-2-8-arrays.html
   * @param lst array
   * @tparam A type
   * @return
   */
  def swapFirstTwoElem[A: ClassTag](lst: Array[A]): Array[A] = lst match {
    case Array(one, two, rest@_*) => Array[A](two, one) ++ rest
    case _ => lst
  }

  def swapFirstTwoElem2[A](lst: Seq[A]) = lst match {
    case Nil => 0
    case one :: two :: rest => two :: one :: rest
    case _ => 0
  }

  println(swapFirstTwoElem(Array(1, 2, 3, 4)).toList)
  println(swapFirstTwoElem(Array(1, 2)).toList)

  println(swapFirstTwoElem2(List(1, 2, 3, 4)))
  println(swapFirstTwoElem2(List(1, 2)))

  // 4
  abstract class Item

  case class Article(description: String, price: Double) extends Item

  case class Bundle(description: String, discount: Double, items: Item*) extends Item

  case class Multiple(quantity: Int, item: Item) extends Item

  def price(it: Item): Double = it match {
    case Article(_, p) => p
    case Bundle(_, disc, its@_*) => disc * its.map(price).sum
    case Multiple(num, item) => num * price(item)
  }

  val special = Bundle("Father's day special", 20.0,
    Article("Scala for the Impatient", 39.95),
    Bundle("Anchor Distillery Sampler", 10.0,
      Article("Old Potrero Straight Rye Whiskey", 79.95),
      Article("Junípero Gin", 32.95)))
  println(price(special), special)
  val multiple = Multiple(5, special)
  println(price(multiple), multiple)

  // 5
  def leafSum(lst: List[Any]): Int = {
    (for (elem <- lst) yield
      elem match {
        case x: Int => x
        case x: List[Any] => leafSum(x)
        case _ => 0
      }
      ).sum
  }

  println(List(List(3, 8), 2, List(5)), leafSum(List(List(3, 8), 2, List(5))))

  // 6
  {
    sealed abstract class BinaryTree

    case class Leaf(value: Int) extends BinaryTree

    case class Node(left: BinaryTree, right: BinaryTree) extends BinaryTree

    def sumBinaryTree(tree: BinaryTree): Int = tree match {
      case Leaf(v) => v
      case Node(left, right) => sumBinaryTree(left) + sumBinaryTree(right)
      case _ => 0
    }

    println(sumBinaryTree(Node(Node(Leaf(3), Leaf(8)), Leaf(5))))
  }

  // 7
  {
    sealed abstract class Tree

    case class Leaf(value: Int) extends Tree

    case class Node(leafs: Tree*) extends Tree

    def sumTree(tree: Tree): Int = tree match {
      case Leaf(v) => v
      case Node(leafs@_*) => leafs.map(sumTree).sum
    }

    println(sumTree(Node(Node(Leaf(3), Leaf(8)), Leaf(2), Node(Leaf(5)))))
  }

  // 8
  {
    sealed abstract class Tree

    case class Leaf(value: Int) extends Tree

    case class Node(op: Char, leafs: Tree*) extends Tree

    def opTree(tree: Tree): Int = tree match {
      case Leaf(v) => v
      case Node(op, leafs@_*) => op match {
        case '+' => leafs.map(opTree).sum
        case '-' => -leafs.map(opTree).sum
        case '*' => leafs.map(opTree).product
      }
    }

    // 3 * 8 + 2 - 5
    println(opTree(Node('+', Node('*', Leaf(3), Leaf(8)), Leaf(2), Node('-', Leaf(5)))))
  }

  // 9
  def sumOptionNotNone(lst: List[Option[Int]]): Int = lst.map(_.getOrElse(0)).sum

  println(sumOptionNotNone(List(Some(1), None, Some(2), None, Some(3))))

  // 10
  def compose(f: Double => Option[Double], g: Double => Option[Double]): Double => Option[Double] = {
    (x: Double) => f(x) match {
      case Some(v) => g(x)
      case None => None
    }
  }

  def f(x: Double) = if (x >= 0) Some(math.sqrt(x)) else None

  def g(x: Double) = if (x != 1) Some(1 / (x - 1)) else None

  val h = compose(f, g)
  println(h(2))
  println(h(1))
  println(h(0))

}
